﻿using iTool.Utils;
using Microsoft.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace iTool.Cloud.NetCore
{

    enum iToolWebSocketState
    {
        None,
        Init,
        Connecting,
        Connected,
        ConnectionError,
        Disconnected,
        Closed
    }

    public abstract class AbstractiToolWebSocketConnection : AbstractiToolSubject
    {
        readonly static RecyclableMemoryStreamManager memoryStreamManager = new RecyclableMemoryStreamManager();
        RecyclableMemoryStream recyclableMemory;
        ClientWebSocket clientWebSocket;
        readonly string gatewayHost;
        ValueWebSocketReceiveResult receiveResult;
        public override event EventHandler<string> OnReceiveEvent;
        public event EventHandler OnReOpenEvent;

        private readonly object reOpenLock = new object();

        private int DelayIndex { get; set; } = 1;


        private iToolWebSocketState webSocketState;

        public AbstractiToolWebSocketConnection(string gatewayHost) 
        {
            this.gatewayHost = gatewayHost;
            this.webSocketState = iToolWebSocketState.None;
        }


        public async Task InitAsync(bool isReOpen = false)
        {
            if (isReOpen)
            {
                clientWebSocket = new ClientWebSocket();
                clientWebSocket.Options.AddSubProtocol(this.ConnectorInfo.UUID);
                try
                {
                    var clientInfo = await this.AESEncryptAsync(this.ConnectorInfo.UUID,
                    new
                    {
                        this.ConnectorInfo.City,
                        this.ConnectorInfo.Province,
                        this.ConnectorInfo.Type,
                        this.ConnectorInfo.Point,
                        this.ConnectorInfo.Platform
                    }.TryToJson());

                    clientWebSocket.Options.AddSubProtocol(clientInfo);
                }
                catch (Exception)
                {
                    throw;
                }
            }

            if (this.ConnectorInfo == null)
            {
                await base.Ini();
                clientWebSocket = new ClientWebSocket();

                this.webSocketState = iToolWebSocketState.Init;

                clientWebSocket.Options.AddSubProtocol(this.ConnectorInfo.UUID);

                try
                {
                    var clientInfo = await this.AESEncryptAsync(this.ConnectorInfo.UUID,
                    new
                    {
                        this.ConnectorInfo.City,
                        this.ConnectorInfo.Province,
                        this.ConnectorInfo.Type,
                        this.ConnectorInfo.Point,
                        this.ConnectorInfo.Platform
                    }.TryToJson());

                    clientWebSocket.Options.AddSubProtocol(clientInfo);
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
        }



        public async Task OpenAsync()
        {
            try
            {
                Console.WriteLine("OpenAsync:" + this.webSocketState.ToString());
                if (this.webSocketState != iToolWebSocketState.Connecting)
                {
                    this.webSocketState = iToolWebSocketState.Connecting;

                    string uri = string.IsNullOrWhiteSpace(this.Token) ? gatewayHost : string.Format("{0}?{1}", gatewayHost, this.Token);
                    //Console.WriteLine(uri);
                    var connectionUri = new Uri(uri);
                    await this.clientWebSocket.ConnectAsync(connectionUri, CancellationToken.None);
                    this.webSocketState = iToolWebSocketState.Connected;
                    this.DelayIndex = 1;

                    HeartbeatAsync();

                    OnReceiveMessageAsync();
                }
            }
            catch (Exception)
            {
                this.webSocketState = iToolWebSocketState.ConnectionError;
                this.DelayIndex = 3;
                await this.ReOpenAsync();
            }
        }

        private async Task ReOpenAsync()
        {
            try
            {
                Console.WriteLine("ReOpenAsync:" + this.webSocketState.ToString());
                if (this.webSocketState == iToolWebSocketState.Disconnected)
                {
                    return;
                }

                lock (reOpenLock)
                {
                    if (this.webSocketState != iToolWebSocketState.Connecting)
                    {
                        this.webSocketState = iToolWebSocketState.Connecting;
                    }
                    else
                    {
                        return;
                    }
                }

                await this.InitAsync(true);

                await Task.Delay(this.DelayIndex * 1000);

                string uri = string.IsNullOrWhiteSpace(this.Token) ? gatewayHost : string.Format("{0}?{1}", gatewayHost, this.Token);
                Console.WriteLine(uri);
                var connectionUri = new Uri(uri);
                await this.clientWebSocket.ConnectAsync(connectionUri, CancellationToken.None);
                this.webSocketState = iToolWebSocketState.Connected;
                this.DelayIndex = 1;


                HeartbeatAsync();

                this.OnReOpenEvent?.Invoke(null, null);

                OnReceiveMessageAsync();


            }
            catch (Exception)
            {
                if (this.DelayIndex < 10)
                {
                    this.DelayIndex++;
                }

                this.webSocketState = iToolWebSocketState.ConnectionError;
                await this.ReOpenAsync();
            }
        }


        public override async Task<bool> SendAsync(string msg)
        {
            if (this.clientWebSocket == null)
                return false;

            if (this.clientWebSocket.State != WebSocketState.Open)
                return false;

            var replyMess = Encoding.UTF8.GetBytes(msg);

            //发送消息
            await this.clientWebSocket.SendAsync(new ArraySegment<byte>(replyMess), WebSocketMessageType.Binary, true, CancellationToken.None);

            return true;
        }

        public async Task CloseAsync()
        {
            await CloseAsync(WebSocketCloseStatus.NormalClosure, "用户手动关闭");
        }

        public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription)
        {
            try
            {
                this.webSocketState = iToolWebSocketState.Disconnected;
                await this.clientWebSocket.CloseAsync(closeStatus, statusDescription, CancellationToken.None);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            this.clientWebSocket.Abort();
            this.clientWebSocket.Dispose();
        }


        // 监听消息
        private async void OnReceiveMessageAsync() 
        {

            List<byte>? data = new List<byte>();

            // 循环接受数据
            while (this.clientWebSocket.State != WebSocketState.Closed)
            {
                // 内存池
                await using (this.recyclableMemory = (RecyclableMemoryStream)memoryStreamManager.GetStream())
                {
                    Memory<byte> memory = this.recyclableMemory.GetMemory();

                    //继续监听Socket信息
                    try
                    {
                        this.receiveResult = await this.clientWebSocket.ReceiveAsync(memory, CancellationToken.None);

                        data.AddRange(memory.ToArray());

                        //消息是否已接收完全
                        if (this.receiveResult.EndOfMessage)
                        {
                            // 发送过来的消息
                            // string userMsg = Encoding.UTF8.GetString(bs.ToArray(), 0, bs.Count);
                            string userMsg = Encoding.UTF8.GetString(data.ToArray(), 0, this.receiveResult.Count);

                            if (OnReceiveEvent != null)
                            {
                                this.OnReceiveEvent.Invoke(null, userMsg);
                            }

                            //清空消息容器
                            data.Clear();
                        }

                    }
                    catch (WebSocketException ex)
                    {
                        switch (ex.WebSocketErrorCode)
                        {
                            case WebSocketError.ConnectionClosedPrematurely:
                            default:
                                Console.WriteLine(ex.WebSocketErrorCode);
                                Console.WriteLine(ex);
                                break;
                        }
                    }

                }

                
            }

            await this.recyclableMemory.DisposeAsync();
            data = null;

            if (this.webSocketState != iToolWebSocketState.Disconnected)
            {
                await this.ReOpenAsync();
            }
        }

        // 启动心跳
        private async void HeartbeatAsync() 
        {
            if (await this.SendAsync("h"))
            {
                await Task.Delay(8000);
            }
            else
            {
                await Task.Delay(2000);
            }

            HeartbeatAsync();
        }
    }
}
